
#ifndef __BMANLIB__WORLD_H__
#define __BMANLIB__WORLD_H__

#include "coid/comm/dynarray.h"
#include "coid/comm/rnd.h"
#include "coid/comm/binstream/filestream.h"
#include "coid/comm/metastream/fmtstreamcxx.h"
#include "coid/comm/dir.h"

#include "tile.h"
#include "moving.h"
#include "man.h"

#include "../rsp/s_room.h"

#include <math.h>

//comment out
extern int TESTING_MODE;

////////////////////////////////////////////////////////////////////////////////
struct World : rsp_base
{
    ///Scheme descriptor
    struct scheme
    {
        charstr _name;              ///< scheme name

        dynarray<uchar> _prob;      ///< probability counts for corresponding powerups
        dynarray<uchar> _pwupinit;  ///< initial counts for corresponding powerups

        dynarray<TILE> _atile;
    };

public:
    enum {
		XTILES				    = 20,	        ///< number of world tiles in row
		YTILES				    = 15,           ///< number of world tiles in column

        MEN                     = 16,           ///< max number of men

        BOMB_DETCOUNTER         = 2200,         ///< time to detonation in msec
        BOMB_DETCOUNTER_BURN    = 600,          ///< time to burn in msec

        DEADBAND_EMPTY          = 0x4000,       ///< half deadband for empty fields
        DEADBAND_WALL           = 0x2000,       ///< half deadband for hard or soft walls

        CMD_GS                  = 1,            ///< code for command - setting game state
    };

    //RSP0 - static stuff
    dynarray<uchar> _prob;          ///< probability counts for corresponding powerups
    dynarray<uchar> _pwupinit;      ///< initial counts for corresponding powerups
    uint    _prob_tot;              ///< sum of probability counts
    uint    _prob_rnd;              ///< random seed

    dynarray<TILE> _atile;
	rnd_int _rnd;                   ///< random generator used to generate starting data

    dynarray<POWERUP> _redistpow;
    
    mutable dynarray<moving*> _nocollist;
    mutable dynarray<man*> _infectlist;
    mutable moving* _collobj;
    mutable int _colltilex;
    mutable int _colltiley;

    //uint    _msecframe;
    ushort  _detcount;
    ushort  _detcount_burn;
    uint    _nroundstowin;

    rsp_dynarray<man,true>  _men;

    uint    _round_msec;            ///< max round time

    uchar   _minplstart;            ///< min.players to start game
    uchar   _minplkeep;             ///< min.connected players to keep playing
    uchar   _maxplayers;            ///< max.players to play

    ///Game state object
    struct GameState : rsp_base
    {
        RSP_CLASS_NAME("G",GameState)
        RSP_NO_FRAMECOMMAND

        int state;                  ///< game state, see the enum below
        uint counter;               ///< game state timeout counter
        uint winner;                ///< last game winner

        void stream_out( binstream& bin )   { bin.xwrite_raw( this, sizeof(*this) ); }
        void stream_in( binstream& bin )    { bin.xread_raw( this, sizeof(*this) ); }
    };

    GameState gs;

    enum {
        GS_BOOTING = 0,             ///< initial server state
        
        GS_GAME_WAIT,               ///< waiting for players to connect
        GS_GAME_START,              ///< enough players, starting game

        GS_ROUND_PREPARE,           ///< a round being prepared - sending scheme, position
        GS_ROUND,                   ///< a round being played
        GS_ROUND_END,               ///< the round ending - score
        GS_ROUND_END_DRAW,          ///< draw game
        GS_ROUND_NOT_ENOUGH_PLAYERS,///< waiting for players to reconnect

        GS_GAME_END,                ///< showing the results
        GS_GAME_END_SCRATCH,        ///< cancelled game


        GST_ROUND_PREPARE           = 1000,
        GST_ROUND_END               = 2000,
        GST_ROUND_NOT_ENOUGH_PLAYERS= 20000,

        GST_GAME_START              = 3000,
        GST_GAME_END                = 5000,
        GST_GAME_END_SCRATCH        = 2000,

        GST_BETWEEN_GAMES           =  100,
    };

    int     _dire;                  ///< suggested direction after call to the try_object


    static dynarray<scheme> _schemes;   ///< server schemes

    static uint _gamesave;          ///< game save id
    static charstr _saveidpath;
    
    struct GameRecord
    {
        timet _begin, _end;
        uint _rounds, _draws;

        metastream _meta;
        fmtstreamcxx _tsav;
        fileiostream _fsav;         ///< save file

        void reset()
        {
            _rounds = 0;
            _draws = 0;

            _begin.now();
        }

        GameRecord() {}
        GameRecord( const GameRecord& ) {}  //do not copy
    };

    GameRecord _grc;


public:
	World();
    ~World() { }

    static ushort get_version();


    void stream_out( binstream& bin )
    {
        MARK_OUT('W');
        _men.stream_out(bin);

        bin << _prob_tot << _prob_rnd << _atile;
        bin.xwrite_raw( _prob.ptr(), _prob.byte_size() );
        bin.xwrite_raw( &_detcount, sizeof(_detcount)+sizeof(_detcount_burn)+sizeof(_nroundstowin) );

        //bin << gs.state << _gscounter << _winner;
        MARK_OUT('W'|0x80);
    }

    void stream_in( binstream& bin )
    {
        MARK_IN('W');
        _men.stream_in(bin);

        bin >> _prob_tot >> _prob_rnd >> _atile;
        bin.xread_raw( _prob.ptr(), _prob.byte_size() );
        bin.xread_raw( &_detcount, sizeof(_detcount)+sizeof(_detcount_burn)+sizeof(_nroundstowin) );

        //bin >> gs.state >> _gscounter >> _winner;
        MARK_IN('W'|0x80);
    }


    void init( uint frame_usec )
    {
        moving::init_constants( frame_usec );

        _detcount = MSEC_TO_TICKS(BOMB_DETCOUNTER);
        _detcount_burn = MSEC_TO_TICKS(BOMB_DETCOUNTER_BURN);
    }

    static rsp_s_room_controller* get_room_controller()
    {
        return  Gactive_controller->room_controller()
            ? (rsp_s_room_controller*) Gactive_controller
            : 0;
    }

    ////////////////////////////////////////////////////////////////////////////////
    opcd rsp_attach( rsp_s_connector& ctrl )
    {
        ctrl.view_add(this);
        ctrl.view_add(&gs);
        _men.rsp_attach(ctrl);

        uint ply = ctrl.room_id();

        introduce_player( ply, ctrl.get_nick() );
        ctrl.set_owned_object( _men[ply] );

        return 0;
    }

    bool rsp_detach( rsp_s_connector& ctrl )
    {
        uint ply = ctrl.room_id();
        rsp_s_object_state* so = rsp_s_object_state::get_from_ptr(_men[ply]);
        _men[ply]->write_disconnect( so->write_commands() );

        return gs.state >= GS_GAME_START  &&  gs.state < GS_GAME_END;
    }

    RSP_CLASS_NAME("W",World)
    ////////////////////////////////////////////////////////////////////////////////
    

	void generate( uint seed, bool round );	///Generate world using specified random number seed

    static uint load_schemes();
    static bool load_scheme( const token& file, scheme& sch );
    void get_scheme();


    void introduce_player( uint ply, const charstr& nick )
    {
        _men[ply]->set_connected(true);
        _men[ply]->touch();

        //if( gs.state == GS_ROUND )
        //    body_enter(&_men[ply]);
    }

    uchar assign_powerup( uint prnd )
    {
        _prob_rnd = 3141592653UL * (_prob_rnd + 1);

        uint prob = (prnd + _prob_rnd) % _prob_tot;
        {for( uint i=0; i<POWERUP::NUM_PWUP; ++i )
        {
            if( _prob[i] > prob )
                return POWERUP::FIRST_POS + (uchar)i;
            else
                prob -= _prob[i];
        }}

        return 0xff;
    }
/*
    void set_tile( uint x, uint y, uchar type, uchar ground )
    {
        TILE* t = get_tile( x, y );
        t->type = type;
        t->ground = ground;
    }
*/
    bool collides_tile( int tx, int ty, const moving* b, bool entry, int* dire ) const
    {
        const TILE* t = get_align_tile( tx, ty );
        if( t->type == TILE::EMPTY )
            return collides_tile_objects( b, *t, tx, ty, entry, dire );

        coord4 p;
        p.x = get_midpoint_coord(tx);
        p.y = get_midpoint_coord(ty);
        if( b->collides_body( TILE::_tilebody, p, dire ) ) {
            _colltilex = tx;
            _colltiley = ty;
            return true;
        }
        return false;
        //return collides_tile_objects( b, tx, ty );
    }

    bool collides_tile_objects( const moving* b, const TILE& t, int tx, int ty, bool entry, int* dire ) const
    {
        bool isman = b->is_man();

        for( uint i=0; i<t._obj.size(); ++i )
        {
            moving* o = t._obj[i];

            if( o == b )  continue;
            if( !o->on_ground() )  continue;
            if( o->in_fire() )  continue;

            if( isman && o->is_man() )
            {
                //check infection distance
                coord d = b->distance_sq(*o);
                if( d < coord(1<<16) )
                    *_infectlist.add() = (man*)o;
                continue;
            }

            coord4 bc;
            if( b->collides_body( *o->_coll, renormalize( *o, tx, ty, bc ), dire ) )
            {
                if( entry && o->is_man() )
                {
                    //entering bombs do not collide
                    *_nocollist.add() = o;
                    continue;
                }

                if( b->_nocollide.contains(o->_boid) >= 0 )
                    *_nocollist.add() = o;
                else
                {
                    _collobj = o;
                    _colltilex = tx;
                    _colltiley = ty;
                    return true;
                }
            }
        }
        return false;
    }

    static coord4& renormalize( const body& b, int tx, int ty, coord4& pb )
    {
        //DASSERT( tx == b._xyzd0.x.whole() );
        //DASSERT( ty == b._xyzd0.y.whole() );
        pb.x = (tx<<16) + b._xyzd0.x.fraction();
        pb.y = (ty<<16) + b._xyzd0.y.fraction();
        return pb;
    }

    void get_tile_coord( const body& b, int* tx, int* ty ) const
    {
        *tx = b._xyzd0.x.whole();
        *ty = b._xyzd0.y.whole();
    }

    static coord get_midpoint_coord( int tile )    { return coord( (tile << 16) + 0x8000 ); }

    TILE* get_tile( const body& b )
    {
        return &_atile[ mody(b._xyzd0.y.whole()) * XTILES + modx(b._xyzd0.x.whole()) ];
    }

    const TILE* get_tile( const body& b ) const
    {
        return &_atile[ mody(b._xyzd0.y.whole()) * XTILES + modx(b._xyzd0.x.whole()) ];
    }

    const TILE* get_tile_ahead( const body& b ) const
    {
        uint dir = b.get_direction();
        return &_atile[
            mody( b._xyzd0.y.whole() + dir::delta_y(dir) ) * XTILES
                + modx( b._xyzd0.x.whole() + dir::delta_x(dir) ) ];
    }

    const TILE* get_tile_next_step( const body& b ) const
    {
        coord x = b._xyzd0.x + b._xyzd1.x + coord( int_change_sign(b._xyzd1.x.as_int(), b._coll->get_radius().as_int()) );
        coord y = b._xyzd0.y + b._xyzd1.y + coord( int_change_sign(b._xyzd1.y.as_int(), b._coll->get_radius().as_int()) );
        return &_atile[ mody( y.whole() ) * XTILES + modx( x.whole() ) ];
    }

    TILE* get_tile( coord x, coord y )
    {
        return &_atile[ mody(y.whole()) * XTILES + modx(x.whole()) ];
    }

    const TILE* get_tile( coord x, coord y ) const
    {
        return &_atile[ mody(y.whole()) * XTILES + modx(x.whole()) ];
    }

    TILE* get_tile ( uint x, uint y )               { return &_atile[(y*XTILES)+x]; }
    const TILE* get_tile ( uint x, uint y ) const   { return &_atile[(y*XTILES)+x]; }

    TILE* get_align_tile ( int x, int y )           { return &_atile[ mody(y)*XTILES + modx(x) ]; }
    const TILE* get_align_tile ( int x, int y ) const { return &_atile[ mody(y)*XTILES + modx(x) ]; }

    static uint mody( int y )   { int m = y%YTILES;  return m<0  ?  YTILES+m  :  m; }
    static uint modx( int x )   { int m = x%XTILES;  return m<0  ?  XTILES+m  :  m; }

/*
    void get_tied_objects( int tx, int ty, const moving* m, dynarray<moving*>& lst ) const
    {
        if( !m->on_ground() )  return;

        const TILE* t = get_align_tile(tx,ty);
        for( uint i=0; i<t->_obj.size(); ++i )
        {
            moving* o = t->_obj[i];
            if( o == m )  continue;

            coord bc[2];
            if( m->collides( *o->_coll, renormalize( *o, tx, ty, bc ), 0 ) )
                *lst.add() = o;
        }
    }
*/
    ///Try if an object can stand on the given place
    /**
        @return 0 if object cannot be placed to the world (collides),
        1 if it can, 2 if there was powerup, and <0 if it can but stands in fire
    */
    int try_object( const moving* b, bool entry, int* dire ) const
    {
        //check against current tile and its objects, and then against
        // neighbour tiles

        _nocollist.reset();
        _infectlist.reset();
        _collobj = 0;

        if( !b->on_ground() )
            return 1;

        int tx, ty;
        get_tile_coord( *b, &tx, &ty );
        if( collides_tile( tx, ty, b, entry, dire ) )
            return 0;
        if( collides_tile( tx+1, ty, b, entry, dire ) )
            return 0;
        if( collides_tile( tx, ty+1, b, entry, dire ) )
            return 0;
        if( collides_tile( tx-1, ty, b, entry, dire ) )
            return 0;
        if( collides_tile( tx, ty-1, b, entry, dire ) )
            return 0;
        if( collides_tile( tx+1, ty+1, b, entry, dire ) )
            return 0;
        if( collides_tile( tx-1, ty+1, b, entry, dire ) )
            return 0;
        if( collides_tile( tx-1, ty-1, b, entry, dire ) )
            return 0;
        if( collides_tile( tx+1, ty-1, b, entry, dire ) )
            return 0;

        const TILE* t = get_align_tile( tx, ty );
        if( t->is_burning() )
            return -1;
        if( t->_pow.size() > 0 )
            return 2;
        return 1;
    }

    /// move object from previous tile (checks if it should move)
    void move_object( moving& obj, TILE* source )
    {
        obj._flags |= moving::fMOVED;
        TILE* dest = get_tile(obj);

        if( source == dest )
            return;

        if( obj._xyzd0.z <= coord(0)  &&  dest->_pow.size() > 0 )
        {
            if( obj.is_man() )
                ((man&)obj).add_powerups( dest->_pow, _redistpow );
            dest->_pow.reset();
        }

        dest->add_object(&obj);
        source->del_object(&obj);
    }

    void set_object( moving* obj )
    {
        TILE* dest = get_tile(*obj);
        dest->add_object(obj);
    }

    bool del_object( moving* obj )
    {
        TILE* dest = get_tile(*obj);
        return dest->del_object(obj);
    }

    void set_bomb( man& m, bool nospooge=false )
    {
        if( m.in_fire() )  return;
        if( m.get_handsup() )  return;

        if( m.is_tied() )
        {
            //check if we can do spooge or a throw
            if( m.get_powerup( POWERUP::GRAB ) )
            {
                if( grab(m) )
                    return;
            }
            else if( !nospooge && m.get_powerup( POWERUP::SPOOGE ) )
            {
                if( set_spooge(m) )
                    return;
            }
        }

        bomb* b = m.get_bomb();
        if(!b)  return;

        coord4 fpos;
        m.get_pos_ahead( fpos, coord(0x1000) );
        b->set_pos( fpos.x.whole(), fpos.y.whole() );// m.get_tile_x(), m.get_tile_y() );

        int ff = try_object( b, true, 0 );
        if( ff <= 0 )
        {
            //TODO: we could do something here: if there's only a bomb colliding, and it's
            // already some distance away and receding, make a tied insert
            // the inserted bomb should not be kickable until tied
            
            coord4 bpos;
            m.get_pos_ahead( bpos, coord(-0x1000) );
            if( bpos.x.whole() != fpos.x.whole()  ||  bpos.y.whole() != fpos.y.whole() )
            {
                b->set_pos( bpos.x.whole(), bpos.y.whole() );
                ff = try_object( b, true, 0 );
            }

            if( ff <= 0 )
            {
                m.bomb_back( b->is_trigger() );
                return;
            }
        }

        tie_object(b);
        set_object(b);

        b->set_counter( m.get_powerup(POWERUP::FASTDETONATE) ? (_detcount>>2) : _detcount );
        b->activate(false);
    }

    bomb* find_tied_bomb( moving* o )
    {
        uint n = o->_nocollide.size();
        for( uint i=0; i<n; ++i )
        {
            moving::boid b = o->_nocollide[i];
            if( b.is_man() )  continue;
            
            return (bomb*)get_object(b);
        }
        return 0;
    }

    moving* get_object( moving::boid o )
    {
        man& m = *_men[o._man];
        return o.is_man() ? (moving*)&m : static_cast<moving*>( m.get_bombs()[o._bombid] );
    }

    bool grab( man& m )
    {
        if( m.is_moving() )
            return false;

        //grab own tied bomb
        bomb* b = (bomb*) find_tied_bomb(&m);
        if( !b  ||  b->get_man() != m.get_man() )  return false;
        //if(!m.on_same_tile(*b))  return false;
        if( b->in_fire() )  return false;
        

        m.bomb_back( b->is_trigger() );
        m.set_handsup(true);

        DASSERT( m.get_usable_bombs() > 0 );

        untie_object(b);
        b->deactivate();
        del_object(b);

        return true;
    }

    bool set_spooge( man& m )
    {
        if( m.get_handsup() || m.is_moving() )
            return false;

        //grab own tied bomb
        bomb* b = (bomb*) find_tied_bomb(&m);
        if(!b)  return false;
        //if(!m.on_same_tile(*b))  return false;

        int tx = b->get_tile_x();
        int ty = b->get_tile_y();
        int dx=0,dy=0;

        switch(m.get_direction())
        {
        case dir::fEAST:  dx=1; break;
        case dir::fWEST:  dx=-1; break;
        case dir::fNORTH: dy=1; break;
        case dir::fSOUTH: dy=-1; break;
        }

        b=0;
        uint i;
        for( i=1; ; ++i )
        {
            tx += dx;
            ty += dy;

            b = m.get_bomb();
            if(!b)  break;

            b->set_pos( tx, ty );

            int ff = try_object( b, true, 0 );
            if( ff == 0 || ff > 1 )
                break;
            if( _nocollist.size() > 0  &&  _nocollist[0]->_boid._man != m._boid._man )
                break;

            tie_object(b);

            b->set_counter( m.get_powerup(POWERUP::FASTDETONATE) ? (_detcount>>2) : _detcount );
            set_object(b);
            b->activate(false);

            b = 0;
        }

        if(b)
            m.bomb_back( b->is_trigger() );

        return i>1;
    }

    ///Punch the bomb or player ahead
    bool punch_bomb( man& m )
    {
        int np = m.get_powerup( POWERUP::PUNCH );
        if( np == 0 )  return false;
/*
        //we can eventually punch our tied object but only if we moved a bit
        bomb* b = (bomb*) find_tied_bomb(&m);

        if( b && !b->in_fire() && m.get_initial_tied_direction() )
        {
            untie_object(b);
            b->punch( m.get_direction(), 3, moving::SPEED_PUNCH );
            return true;
        }
*/
        coord4 pa;
        m.get_pos_ahead( pa, m._coll->get_radius() + bomb::get_bomb_radius() );

        TILE* t = get_align_tile( pa.x.whole(), pa.y.whole() );
        for( uint i=0; i<t->_obj.size(); ++i )
        {
            moving* o = t->_obj[i];
            if( &m == o )  continue;            //not itself
            if( !o->on_ground() )  continue;    //can't punch what's in air
            if( o->in_fire() )  continue;       //can't punch burning stuff
            
            if( !o->is_man() )
            {
                untie_object(o);
                o->punch( m.get_direction(), 3, moving::SPEED_PUNCH );
                return true;
            }
        }

        if( np>1 )
        {
            for( uint i=0; i<MEN; ++i )
            {
                man* x = _men[i];
                if( x == &m )  continue;
                if( !x->in_world() || !x->on_ground() || x->in_fire() )  continue;

                if( (pa.x - x->_xyzd0.x).abs() > bomb::get_bomb_radius()
                 || (pa.y - x->_xyzd0.y).abs() > bomb::get_bomb_radius() )  continue;

                untie_object(x);
                x->punch( m.get_direction(), 4, moving::SPEED_PUNCH_MAN );
                return true;
            }
        }

        return false;
    }

    ///Throw bomb
    void throw_bomb( man& m )
    {
        if( !m.get_handsup() )  return;

        //int np = m.get_powerup( POWERUP::GRAB );
        //if( np == 0 )  return;

        bomb* b = m.get_bomb();
        if(!b) return;
        //DASSERT(b);

        m.set_handsup(false);

        coord4 bpos;
        m.get_pos_ahead( bpos, coord(0x1000) );
        b->set_pos( bpos.x.whole(), bpos.y.whole(), 1 );

        TILE* dest = get_tile(*b);
        dest->add_object(b);

        b->set_counter( m.get_powerup(POWERUP::FASTDETONATE) ? (_detcount>>2) : _detcount );
        b->activate(false);

        b->punch( m.get_direction(), 3, moving::SPEED_PUNCH );
    }

    ///Explode or implode flame
    /**
        @param x,y  tile position
        @param ply  player number
        @param len  flame length
        @param size array of size 4, actual flame lengths: +x-x+y-y
    **/
    void flame_explode( uint x, uint y, uchar plybomb, uchar ignitor, int len, uchar* size );
    void flame_implode( uint x, uint y, int len, const uchar* size );

    ///Set flame from player ply on tile x,y
    int flame_set( uint x, uint y, uchar plybomb, uchar ignitor, bool hflame, bool vflame, int8 d )
    {
        TILE* t = get_align_tile( x, y );

        if( hflame && vflame )
        {
            t->set_flamehv(plybomb);
            return 1;
        }

        int rv;
        if(hflame)  rv = t->set_flameh(plybomb,d,ignitor);
        if(vflame)  rv = t->set_flamev(plybomb,d,ignitor);

        return rv;
    }

    ///Clear flame from player ply on tile x,y
    bool flame_clr( uint x, uint y, bool hflame, bool vflame, int8 d )
    {
        TILE* t = get_align_tile( x, y );

        if( hflame && vflame )
        {
            t->clr_flamehv();
            return true;
        }

        bool rv;
        if(hflame)  rv = t->clr_flameh(d);
        if(vflame)  rv = t->clr_flamev(d);

        if(!rv)
            generate_powerup( t, x, y );

        return rv;
    }

    void generate_powerup( TILE* t, uint x, uint y );

    void redistribute_powerups();

    // body methods
    bool body_enter( moving* b )//, moving* tied )
    {
/*        if( tied )
        {
            b._tied = tied;
            tied->_tied = &b;
        }
*/
        if( b->in_world() )
            return true;

        int ff = try_object( b, true, 0 );
        if( ff == 0 )
            return false;

        tie_object(b);
        set_object(b);

        b->activate( ff<0 );
        return true;
    }

    void body_leave( moving* b )
	{
        if( !b->in_world() )
			return;
	  	del_object(b);
        untie_object(b);
        b->deactivate();
    }

    void adjust_speed( moving* m )
    {
        if( m->is_man() )
            ((man*)m)->compute_speed();
    }

    ///Tie to objects in the list previously returned by try_object
    void tie_object( moving* b )
    {
        for( uint i=0; i<_nocollist.size(); ++i )
        {
            b->nocollide_add(_nocollist[i]);
            _nocollist[i]->nocollide_add(b);
        }
    }

    void untie_object( moving* o )
    {
        dynarray<moving::boid>& nc = o->_nocollide;
        uint n = nc.size();

        for( uint i=0; i<n; ++i )
        {
            man& m = *_men[ nc[i]._man ];
            if( nc[i]._bombid == 0xff ) //a player itself
                m.nocollide_del(o);
            else
                m.get_bombs()[ nc[i]._bombid ]->nocollide_del(o);
        }

        nc.reset();
    }

    void retie_object( moving* b )
    {
        if( _nocollist.size() != b->_nocollide.size() )  { untie_object(b); tie_object(b); return; }

        for( uint i=0; i<_nocollist.size(); ++i )
        {
            if( _nocollist[i]->_boid != b->_nocollide[i] )
            {   untie_object(b); tie_object(b); return; }
        }
    }


    void infect( man* m )
    {
        if( _infectlist.size() == 0 )  return;

        for( uint i=POWERUP::CRAWL; i<=POWERUP::COMBINED; ++i )
        {
            if( m->get_powerup(i) )
                infect_men( i, m->get_ticks() );
        }
    }

    void infect_men( uint pow, uint ticks )
    {
        for( uint i=0; i<_infectlist.size(); ++i )
            if( _infectlist[i]->get_powerup(pow) == 0 ) //don't infect twice
                _infectlist[i]->add_powerup( pow, (((_rnd.rand()&0xff)|0x80)*ticks)>>8, _redistpow );
    }

    /// @return true if field in given direction from object isn't free or it has a
    ///  stopped bomb 
    bool obstacle_ahead( moving& m )
    {
        const TILE* t = get_tile_ahead(m);
        return t->is_obstacle();
    }

    bool obstacle_tile_next_step( moving& m )
    {
        const TILE* t = get_tile_next_step(m);
        return t->is_obstacle_tile();
    }

    ///@return false if stopped because of an obstacle
    bool body_tick( moving* b )
    {
        TILE* src = get_tile(*b);
        DASSERT( !b->in_world() || src->_obj.contains(b) != -1 );

        if( b->_xyzd0.z <= coord(0)  &&  b->_xyzd1.x == coord(0)  &&  b->_xyzd1.y == coord(0) )
        {
            //we're standing still

            if( src->is_burning() && !b->in_fire() )
                set_burning( b, src->fire_owner() );

            if( !b->is_man() )
                check_explosion( (bomb*)b );
            
            if( b->is_tied() )
            {
                try_object( b, false, 0 );
                retie_object(b);
            }

            return true;
        }

        bool wasinair = b->_xyzd0.z > coord(0);
        coord4 cold = b->_xyzd0;

        //if misaligned in current direction, slowly adjust the position
        if( !obstacle_ahead(*b) )
            b->adjust_misalignment();

        bool nostep=false;
        if( b->is_tied() && b->is_man() )
        {
            //tied man would:
            // - align when moving away from the tied bomb
            // - jump outside the bomb if past certain distance (half the bomb radius)
            bomb* bo = find_tied_bomb(b);
            if( bo->get_man() == b->get_man() )     //only with tied bombs of our own
            {
                coord qd = b->is_towards( bo->_xyzd0 );
                if( qd < coord(0) )
                {
                    //outwards
                    b->_flags |= moving::fTIED_OUT;
                    b->adjust_retraction_misalignment();
                }
                else if( (b->_flags & moving::fTIED_OUT)  &&  qd >= coord(1<<12) )
                {
                    b->_flags &= ~moving::fTIED_OUT;
                    //towards
                    //step outside if possible, it's aligned already
                    switch( b->get_direction() )
                    {
                    case dir::fEAST: b->_xyzd0.x = bo->_xyzd0.x - bo->_coll->get_radius() - b->_coll->get_radius(); break;
                    case dir::fWEST: b->_xyzd0.x = bo->_xyzd0.x + bo->_coll->get_radius() + b->_coll->get_radius(); break;
                    case dir::fNORTH: b->_xyzd0.y = bo->_xyzd0.y - bo->_coll->get_radius() - b->_coll->get_radius(); break;
                    case dir::fSOUTH: b->_xyzd0.y = bo->_xyzd0.y + bo->_coll->get_radius() + b->_coll->get_radius(); break;
                    }

                    nostep = true;
                    DASSERT( !b->_coll->collides( b->_xyzd0, *bo->_coll, bo->_xyzd0 ) );
                }
            }
        }

        if(!nostep)
            b->step_fwd();

        if( b->_xyzd0.z > coord(0) )
        {
            //in air
            b->deccelerate();
            move_object( *b, src );
            DASSERT( !b->in_world() || get_tile(*b)->_obj.contains(b) != -1 );
            return true;
        }
        else if(wasinair)
        {
            b->align();
            move_object( *b, src );
        }

        b->_xyzd0.z = 0;

        int dire[2];
        dire[0] = b->get_direction();
        dire[1] = 0;

        int ff = try_object( b, false, dire );
        if( ff == 0             //cannot go
            || (wasinair && ff == 2) )
        {
            //b->step_bkw();      //undo
            b->_xyzd0 = cold;
            b->_perpend = 0;

            if(wasinair)
            {
                uint d=0;
                if( b->get_jelly() && _collobj )
                {
                    //d = 1 << (((3141592653UL * Gactive_controller->get_current_stateid()) >> 17) & 3);
                    d = _rnd.rand();
                    if( ((d>>27) & 1) == 0 )
                    {
                        d = (d>>7) + (d>>17);
                        d = 1 << ( d & 3 );
                    }
                    else
                        d = 0;
                }

                b->punch( d, 1 , moving::SPEED_PUNCH );

                _dire = dire[1];
                return true;
            }

            if( !b->is_man() )
                b->stop();

            if( src->is_burning() && !b->in_fire() )
                set_burning( b, src->fire_owner() );

            if( !b->is_man() )
                check_explosion( (bomb*)b );
            
            _dire = dire[1];
            return false;
        }

        retie_object(b);

        // can go
        move_object( *b, src );
        DASSERT( !b->in_world() || get_tile(*b)->_obj.contains(b) != -1 );

        infect( (man*)b );

        if( ff < 0 )    //in fire
            set_burning( b, src->fire_owner() );

        if( !b->is_man() )
            check_explosion( (bomb*)b );
/*        
        if( b->_tied.is_set() )
        {
            if( !collides_tied_object(b) )
                b->untie();
        }
*/
        // THIS SHOULD BE AN EVENT
        //if( b._xyzd1[2] )   //just fell on the surface
        //    b._flags |= moving::fDROPPED;

        b->stop_fall();
        return true;
    }

    ///
    bool man_tick( man* m )
    {
        bool cango = body_tick(m);

        if( m->is_dying() && m->get_ticks() == 0 )
            m->fetch_powerups( _redistpow );

        if(cango)  return cango;

/*
        if(cango)
        {

            //check if we could kick a tied bomb
            if( m->is_tied()  &&  m->get_powerup(POWERUP::KICK) > 0 )
            {
                uint dold = m->get_initial_tied_direction();
                uint dire = m->get_direction_or_stop();

                if( dold == UMAX ) {
                    //already used
                    return true;
                }

                bomb* b = find_tied_bomb(m);

                if( dold == 0 )
                {
                    //initial tied dir wasn't set yet
                    if( !obstacle_ahead(*b,dire) )
                        m->set_initial_tied_direction();
                    return true;
                }

                //swap dold bits for overlap check
                dold = ((dold&0x0a)>>1) | ((dold&0x05)<<1);

                if( dire & dold )
                {
                    //change in speed in opposite direction
                    if( !obstacle_ahead(*b,dire) ) {
                        b->set_direction( dire, dire );
                        m->set_initial_tied_direction_used();
                    }
                    else
                        m->set_initial_tied_direction_clear();
                }
                else if(dire)
                {
                    if( !obstacle_ahead(*b,dire) )
                        m->set_initial_tied_direction();
                    else
                        m->set_initial_tied_direction_clear();
                }
            }

            return true;
        }
*/
        //we were stopped because of an obstacle
        // check if we aren't in a dead band and try to get around the corners
        //the dead band for empty fields in front of the man should be wider,
        // to avoid too easy sideslip around a bomb

        //colliding with a bomb but being able to kick
        if( _collobj  &&  m->get_powerup(POWERUP::KICK) > 0 )
        {
            if( _dire==0 )
            {
                ((bomb*)_collobj)->kick(*m);
                m->start();
                return false;
            }
            /*else
            {
                int tx = _collobj->get_tile_x();
                int ty = _collobj->get_tile_y();

                switch(_dire)
                {
                case dir::fEAST:    ++tx;  break;
                case dir::fWEST:    --tx;  break;
                case dir::fNORTH:   ++ty;  break;
                case dir::fSOUTH:   --ty;  break;
                }

                const TILE* ta = get_align_tile( tx, ty );
                if( ta->type != TILE::EMPTY  ||  ta->has_bomb() ) {
                    _collobj->set_direction( m->get_direction(), m->get_direction() );
                    m->start();
                    return false;
                }
            }*/
        }

        //no exit direction suggested
        if( _dire == 0 )
        {
            //if there is a perpendicular direction active
            uint df = m->get_full_direction();
            _dire = (df>>8) & 0xff & ~df;

            if(!_dire)
                return false;
        }

        //colliding with a brick: check if the suggested direction is perspective
        if(!_collobj)
        {
            switch(_dire)
            {
            case dir::fEAST:    ++_colltilex;  break;
            case dir::fWEST:    --_colltilex;  break;
            case dir::fNORTH:   ++_colltiley;  break;
            case dir::fSOUTH:   --_colltiley;  break;
            }

            const TILE* ta = get_align_tile( _colltilex, _colltiley );
            if( ta->type != TILE::EMPTY )
            {
                //not perspective, lets try the perpendicular direction if any
                uint df = m->get_full_direction();
                _dire = (df>>8) & 0xff & ~df;

                if(!_dire)
                    return false;
            }
        }

        m->set_direction_temp(_dire);

        TILE* src = get_tile(*m);

        m->step_fwd();

        int ff = try_object( m, false, 0 );
        if( ff <= 0 )           //cannot go or in fire
        {
            m->step_bkw();   //undo
            m->reset_direction_temp();

            return false;
        }

        retie_object(m);
        m->reset_direction_temp();

        // can go
        move_object( *m, src );
/*
        if( m->_tied.is_set() )
        {
            if( !collides_tied_object(m) )
                m->untie();
        }
*/
        return true;
    }

    bool bomb_tick( bomb* b )
    {
        if( body_tick(b) )  return true;

        if( b->get_jelly() )
        {
            uint d = b->get_reverse_direction();
            b->set_direction( d, d );
            b->start();
            return true;
        }

        return try_align_object(b);
    }


    bool try_align_object( moving* b )
    {
        coord x = b->_xyzd0.x;
        coord y = b->_xyzd0.y;

        b->align_temp();
        int ff = try_object( b, false, 0 );
        if( ff != 0 )
            b->stop();
        else {
            b->_xyzd0.x = x;
            b->_xyzd0.y = y;
            return false;
        }

        return true;
    }

    void stop_bombs( man& m )
    {
        if( m.get_powerup(POWERUP::JELLY) > 0 )  return;
        
        for( uint i=0; i<man::MAXBOMBS; ++i )
        {
            const rsp_dynarray<bomb,true>& bombs = m.get_bombs();
            if( !bombs[i]->is_stopped()  &&  bombs[i]->_xyzd0.z <= coord(0) )
            {
                if( try_align_object( bombs[i] ) )
                    break;
            }
        }
    }

    void set_burning( moving* mv, uchar plybomb )
    {
        /*
            input:
            killing flame color (kfc)       - color of the flame directly causing the death
            man color (mc)                  - dying man color
            ignitor color (ic)              - color of bomb that initiated the detonation
            last touching man color (ltc)   - color of man that changed the direction of the bomb as last
        */

        if( mv->in_fire() )
            return;

        if( mv->is_man() )
        {
            bomb& b = get_bomb_by_uid(plybomb);

            uchar kfc = plybomb & TILE::xMANID;
            uchar mc = mv->get_man();
            uchar ic = b.get_ignitor() & TILE::xMANID;
            uchar ltc = b.get_last_touch();


            //own bomb, if man touched the bomb as last, suicide for him and kill for the ignitor, providied it's not himself
            if( kfc == mc && ltc == mc )
            {
                ++_men[mc]->_nsuicides;
                if( ic != mc )
                    ++_men[ic]->_nkills;
            }

            //if it's his bomb that killed, but he wasn't last to touch it
            if( kfc == mc )
            {
                //surely here ltc != kfc
                ++_men[mc]->_nsuicides;
                ++_men[ltc]->_nkills;
            }
            else
            {
                //it wasn't his bomb that killed him
                // if he messed with the bomb it's not counted as a suicide unless the bomb has different ignitor
                // ltc has priority over kfc unless it equals mc
                if( ltc != mc )
                    ++_men[ltc]->_nkills;
                //ltc == mc, he messed with it
                else if( ic != kfc )    //different ignitor, suicide 
                    ++_men[mc]->_nsuicides,
                    ++_men[kfc]->_nkills;
                else                    //same ignitor
                    ++_men[kfc]->_nkills;
            }

            
            ((man*)mv)->burn();
        }
        else
        {
            //fetch the original ignitor
            bomb& b = get_bomb_by_uid(plybomb);

            mv->stop();
            ((bomb*)mv)->prepare_detonation( b.get_ignitor(), 0 );
        }
    }

    bomb& get_bomb_by_uid( uchar uid )
    {
        return *_men[ uid & TILE::xMANID ]->get_bombs()[ uid >> TILE::rBOMBID ];
    }


    void check_explosion( bomb* bm )
    {
        if( bm->get_counter() == 0  ||  (bm->get_flame()>0 && bm->in_fire()) )
        {
            if( bm->get_flame()>0 )   //differetiate between phases
            {
                //explode
                short fl = bm->get_flame();
                bm->burn();
                
                flame_explode( bm->get_tile_x(), bm->get_tile_y(),
                    bm->get_unique_bombid(),
                    bm->get_ignitor(),
                    fl, bm->get_flsize_ptr() );

                bm->set_counter(_detcount_burn);
                _men[bm->get_man()]->bomb_burning();
            }
            else
            {
                flame_implode( bm->get_tile_x(), bm->get_tile_y(),
                    -bm->get_flame(), bm->get_flsize_ptr() );
                
                untie_object(bm);
                bm->deactivate();
                del_object(bm);

                _men[bm->get_man()]->bomb_went_off();
            }
        }
    }

    void map()
    {
        uint n = _atile.size();
        if( n < XTILES*YTILES )
            return;

        for( uint i=0; i<n; ++i )
            _atile[i]._obj.reset();
        
        for( uint mi=0; mi<MEN; ++mi )
        {
            man& m = *_men[mi];

            if( m.in_world() )
                get_tile(m)->add_object(&m);

            const rsp_dynarray<bomb,true>& ba = m.get_bombs();
            for( uint b=0; b<ba.size(); ++b )
            {
                bomb& bm = *ba[b];
                if( bm.in_world() )
                    get_tile(bm)->add_object(&bm);
            }
        }
    }

    ///Go through all objects in the playground and update them
    virtual void frame();

    virtual void command( uchar cd, binstream& cmd )
    {
    }

    ///Game state machine processing
    void process_gamestate();

    void set_gamestate( int gs, uint timeout );
    void game_begin();
    void game_end( bool scratched );

private:

    void mark_active_men();

    void load_default_pwup_probs();
    void load_default_pwup_counts();
    void compute_prob_total();

    bool go_above_deadband( coord c, int dband, bool hasbomb, uint tx, uint ty ) const
    {
        //we are in the dead band
        if( (int)c.fraction() - 0x8000L < dband )
        {
            //wall ahead
            if( dband == DEADBAND_WALL )  return false;
            //empty field ahead, and we still colliding
            else if( hasbomb )  return false;
        }

        //empty field ahead, and we still colliding
        if( !hasbomb  &&  dband == DEADBAND_EMPTY  &&  c.fraction() < 0x8000 )  return true;

        const TILE* t = get_align_tile(tx,ty);
        //a non-empty field near
        if( t->type != TILE::EMPTY )  return false;
        //wall ahead (and empty field near)
        if( dband == DEADBAND_WALL )  return true;

        return !t->has_bomb();
    }

    bool go_below_deadband( coord c, int dband, bool hasbomb, uint tx, uint ty ) const
    {
        //we are in the dead band
        if( 0x8000L - (int)c.fraction() < dband )
        {
            //wall ahead
            if( dband == DEADBAND_WALL )  return false;
            //empty field ahead, and we still colliding
            else if( hasbomb )  return false;
        }

        //empty field ahead, and we still colliding
        if( !hasbomb  &&  dband == DEADBAND_EMPTY  &&  c.fraction() > 0x8000 )  return true;
        
        const TILE* t = get_align_tile(tx,ty);
        //a non-empty field near
        if( t->type != TILE::EMPTY )  return false;
        //wall ahead (and empty field near)
        if( dband == DEADBAND_WALL )  return true;

        return !t->has_bomb();
    }

    void enter_gamestate_round_prepare( bool round );

    void load_gamesave_id()
    {
        if( _saveidpath.is_empty() ) {
            _saveidpath << "save";
            directory::mkdir(_saveidpath);
            _saveidpath << directory::separator() << "save.id";
        }

        bifstream fio(_saveidpath);
        if( !fio.is_open() )
            _gamesave = 0;
        else
        {
            try { fio >> _gamesave; }
            catch(opcd) { _gamesave=0; }
        }
    }

    void save_gamesave_id()
    {
        bofstream fio(_saveidpath);
        if( fio.is_open() )
        {
            opcd e;
            try { fio << _gamesave; }
            catch(opcd exc) { e = exc; }

            if(!e)  return;
        }

        LOG(0) { *TLOG << "ERROR: can't save " << _saveidpath << BINSTREAM_FLUSH; }
    }
};


//extern World Gworld;

#endif //__BMANLIB__WORLD_H__
